/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package io.netty.buffer;

import io.netty.util.Recycler;
import io.netty.util.Recycler.Handle;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

abstract class PooledByteBuf<T> extends AbstractReferenceCountedByteBuf {

  private final Recycler.Handle<PooledByteBuf<T>> recyclerHandle;

  protected PoolChunk<T> chunk;
  protected long handle;
  protected T memory;
  protected int offset;
  protected int length;
  int maxLength;
  PoolThreadCache cache;
  ByteBuffer tmpNioBuf;
  private ByteBufAllocator allocator;

  @SuppressWarnings("unchecked")
  protected PooledByteBuf(Recycler.Handle<? extends PooledByteBuf<T>> recyclerHandle,
      int maxCapacity) {
    super(maxCapacity);
    this.recyclerHandle = (Handle<PooledByteBuf<T>>) recyclerHandle;
  }

  void
  init(PoolChunk<T> chunk, ByteBuffer nioBuffer,
      long handle, int offset, int length, int maxLength, PoolThreadCache cache) {
    init0(chunk, nioBuffer, handle, offset, length, maxLength, cache);
  }

  void initUnpooled(PoolChunk<T> chunk, int length) {
    init0(chunk, null, 0, chunk.offset, length, length, null);
  }

  private void init0(PoolChunk<T> chunk, ByteBuffer nioBuffer,
      long handle, int offset, int length, int maxLength, PoolThreadCache cache) {
    assert handle >= 0;
    assert chunk != null;

    this.chunk = chunk;
    memory = chunk.memory;
    tmpNioBuf = nioBuffer;
    allocator = chunk.arena.parent;
    this.cache = cache;
    this.handle = handle;
    this.offset = offset;
    this.length = length;
    this.maxLength = maxLength;
  }

  /**
   * Method must be called before reuse this {@link PooledByteBufAllocator}
   */
  final void reuse(int maxCapacity) {
    maxCapacity(maxCapacity);
    setRefCnt(1);
    setIndex0(0, 0);
    discardMarks();
  }

  @Override
  public final int capacity() {
    return length;
  }

  @Override
  public final ByteBuf capacity(int newCapacity) {
    checkNewCapacity(newCapacity);

    // If the request capacity does not require reallocation, just update the length of the memory.
    if (chunk.unpooled) {
      if (newCapacity == length) {
        return this;
      }
    } else {
      if (newCapacity > length) {
        if (newCapacity <= maxLength) {
          length = newCapacity;
          return this;
        }
      } else if (newCapacity < length) {
        if (newCapacity > maxLength >>> 1) {
          if (maxLength <= 512) {
            if (newCapacity > maxLength - 16) {
              length = newCapacity;
              setIndex(Math.min(readerIndex(), newCapacity), Math.min(writerIndex(), newCapacity));
              return this;
            }
          } else { // > 512 (i.e. >= 1024)
            length = newCapacity;
            setIndex(Math.min(readerIndex(), newCapacity), Math.min(writerIndex(), newCapacity));
            return this;
          }
        }
      } else {
        return this;
      }
    }

    // Reallocation required.
    chunk.arena.reallocate(this, newCapacity, true);
    return this;
  }

  @Override
  public final ByteBufAllocator alloc() {
    return allocator;
  }

  @Override
  public final ByteOrder order() {
    return ByteOrder.BIG_ENDIAN;
  }

  @Override
  public final ByteBuf unwrap() {
    return null;
  }

  @Override
  public final ByteBuf retainedDuplicate() {
    return PooledDuplicatedByteBuf.newInstance(this, this, readerIndex(), writerIndex());
  }

  @Override
  public final ByteBuf retainedSlice() {
    final int index = readerIndex();
    return retainedSlice(index, writerIndex() - index);
  }

  @Override
  public final ByteBuf retainedSlice(int index, int length) {
    return PooledSlicedByteBuf.newInstance(this, this, index, length);
  }

  protected final ByteBuffer internalNioBuffer() {
    ByteBuffer tmpNioBuf = this.tmpNioBuf;
    if (tmpNioBuf == null) {
      this.tmpNioBuf = tmpNioBuf = newInternalNioBuffer(memory);
    }
    return tmpNioBuf;
  }

  protected abstract ByteBuffer newInternalNioBuffer(T memory);

  @Override
  protected final void deallocate() {
    if (handle >= 0) {
      final long handle = this.handle;
      this.handle = -1;
      memory = null;
      chunk.arena.free(chunk, tmpNioBuf, handle, maxLength, cache);
      tmpNioBuf = null;
      chunk = null;
      recycle();
    }
  }

  private void recycle() {
    recyclerHandle.recycle(this);
  }

  protected final int idx(int index) {
    return offset + index;
  }
}
